package org.modelcc.io.java.checker;


import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import org.modelcc.io.java.JavaModelReader;
import org.modelcc.io.java.JavaModelChecker;
import org.modelcc.io.java.JavaLanguageMetadata;
import org.modelcc.language.metamodel.SimpleLanguageElement;
import org.modelcc.language.metamodel.MemberCollection;
import org.modelcc.language.metamodel.CompositeLanguageElement;
import org.modelcc.language.metamodel.LanguageElement;
import org.modelcc.language.metamodel.LanguageMember;
import org.modelcc.lexer.recognizer.PatternRecognizer;

/**
 * Cycle checker
 */
public class CycleChecker extends JavaModelChecker<JavaLanguageMetadata> 
{	
	public CycleChecker (JavaModelReader reader)
	{
		super(reader);
	}
	
	@Override
	public void check(JavaLanguageMetadata metadata) 
	{
    	for (LanguageElement e: metadata.elements) {
    		checkCycle(e,e,metadata.classToElement,metadata.subclasses);
    	}		
    }
	
    private void checkCycle(LanguageElement es, LanguageElement orig,
    		Map<Class, LanguageElement> classToElement,
    		Map<LanguageElement, Set<LanguageElement>> subclasses) 
    {
    	if ((es.getPrefix()!=null)) {
    		for (PatternRecognizer p: es.getPrefix()) {
    			if (p.read("",0) == null) {
    				return;
    			}
    		}
    	}
    	
    	if ((es.getSuffix()!=null)) {
    		for (PatternRecognizer p: es.getSuffix()) {
    			if (p.read("",0) == null) {
    				return;
    			}
    		}
    	}
    	
    	if (SimpleLanguageElement.class.isAssignableFrom(es.getClass())) {
    		return;
    	} else if (CompositeLanguageElement.class.isAssignableFrom(es.getClass())) {
    		checkComplexModelElement( (CompositeLanguageElement)es, orig, classToElement, subclasses);
    	} else { 
    		checkAbstractModelElement( es, orig, classToElement, subclasses);
    	}
    }

    
	private void checkComplexModelElement(CompositeLanguageElement ces, LanguageElement orig,
			Map<Class, LanguageElement> classToElement,
			Map<LanguageElement, Set<LanguageElement>> subclasses) 
	{
		for (int i = 0; i < ces.getMembers().size();i++) {
			LanguageMember em = ces.getMembers().get(i);
			if (!em.isOptional()) {
				LanguageElement emca = classToElement.get(em.getElementClass());
				if (emca.getPrefix()!=null) {
					for (Iterator<PatternRecognizer> ite = emca.getPrefix().iterator();ite.hasNext();) {
						if (ite.next().read("",0) == null) {
							return;
						}
					}
				}
				if (emca.getSuffix()!=null) {
					for (Iterator<PatternRecognizer> ite = emca.getSuffix().iterator();ite.hasNext();) {
						if (ite.next().read("",0) == null) {
							return;
						}
					}
				}
				if (em.getPrefix()!=null) {
					for (Iterator<PatternRecognizer> ite = em.getPrefix().iterator();ite.hasNext();) {
						if (ite.next().read("",0) == null) {
							return;
						}
					}
				}
				if (em.getSuffix()!=null) {
					for (Iterator<PatternRecognizer> ite = em.getSuffix().iterator();ite.hasNext();) {
						if (ite.next().read("",0) == null) {
							return;
						}
					}
				}
				if (MemberCollection.class.isAssignableFrom(em.getClass())) {
					MemberCollection mem = (MemberCollection)em;
					if (mem.getMinimumMultiplicity()>0) {
						if (em.getSeparator()!=null) {
							for (Iterator<PatternRecognizer> ite = em.getSeparator().iterator();ite.hasNext();) {
								if (ite.next().read("",0) == null) {
									return;
								}
							}
						}
					}
				}
			}
		}
		
		boolean hasOtherThanOrig = false;
		boolean hasOrig = false;

		for (int i = 0;i < ces.getMembers().size();i++) {
			LanguageMember em = ces.getMembers().get(i);
			LanguageElement emm = classToElement.get(em.getElementClass());
			boolean onlyOne = false;
			if ((MemberCollection.class.isAssignableFrom(em.getClass()))) {
				MemberCollection mem = (MemberCollection)em;
				if (mem.getMinimumMultiplicity()<=1)
					onlyOne = true;
			} else {
				onlyOne = true;
			}
			if (!em.isOptional()) {
				if (emm.equals(orig)) {
					if (onlyOne)
						hasOrig = true;
					if ((emm.getPrefix()!=null)) {
						for (Iterator<PatternRecognizer> ite2 = emm.getPrefix().iterator();ite2.hasNext();) {
							if (ite2.next().read("",0) == null) {
								hasOtherThanOrig = true;
							}
						}
					}
					if ((emm.getSuffix()!=null)) {
						for (Iterator<PatternRecognizer> ite2 = emm.getSuffix().iterator();ite2.hasNext();) {
							if (ite2.next().read("",0) == null) {
								hasOtherThanOrig = true;
							}
						}
					}
				} else {
					hasOtherThanOrig = true;
				}
			}
		}

		if (hasOrig && !hasOtherThanOrig)
			log(Level.SEVERE, "Class \"{0}\" cycles with recursive inheritance or composition.", new Object[]{orig.getElementClass().getCanonicalName()});

		if (hasOrig && !hasOtherThanOrig) {
			for (int i = 0;i < ces.getMembers().size();i++) {
				LanguageMember em = ces.getMembers().get(i);
				LanguageElement emm = classToElement.get(em.getElementClass());
				if (!em.isOptional()) {
					if (!emm.equals(orig)) {
						checkCycle(emm,orig,classToElement,subclasses);
					}
				}
			}
		}
	}

	private void checkAbstractModelElement(
			LanguageElement ces, LanguageElement orig,
			Map<Class, LanguageElement> classToElement,
			Map<LanguageElement, Set<LanguageElement>> subclasses) 
	{
		if (subclasses.containsKey(ces)) {
			boolean hasOtherThanOrig = false;
			boolean hasOrig = false;
			for (Iterator<LanguageElement> ite = subclasses.get(ces).iterator();ite.hasNext();) {
				LanguageElement me = ite.next();
				if (me.equals(orig)) {
					hasOrig = true;
					if ((me.getPrefix()!=null)) {
						for (Iterator<PatternRecognizer> ite2 = me.getPrefix().iterator();ite2.hasNext();) {
							if (ite2.next().read("",0) == null) {
								hasOtherThanOrig = true;
							}
						}
					}
					if ((me.getSuffix()!=null)) {
						for (Iterator<PatternRecognizer> ite2 = me.getSuffix().iterator();ite2.hasNext();) {
							if (ite2.next().read("",0) == null) {
								hasOtherThanOrig = true;
							}
						}
					}
				} else {
					hasOtherThanOrig = true;
				}
				checkCycle(me,orig,classToElement,subclasses);
			}
			if (hasOrig && !hasOtherThanOrig) {
				log(Level.SEVERE, "Class \"{0}\" cycles with recursive inheritance or composition.", new Object[]{orig.getElementClass().getCanonicalName()});
			}
		}
	}
	
}
